// Copyright (c) 2009 Luca Marrocco.
// Modified or written by Luca Marrocco for inclusion with codej.
// Licensed under the Apache License, Version 2.0 (the "License")
package codej.ast.factory;

import java.lang.reflect.*;

/** @author Luca Marrocco */
public class ObservableNodeFactory extends DefaultNodeFactory {

	class ObservableNode implements InvocationHandler {
		private Object node;

		public ObservableNode(Class aClass) {
			try {
				node = aClass.newInstance();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}

		public Class[] classes(Object[] objects) {
			Class[] classes = new Class[objects.length];

			for (int i = 0; i < objects.length; i++)
				classes[i] = objects[i].getClass();

			return classes;
		}

		public boolean hasMethod(Object object, String methodName, Object[] parameters) {
			try {
				node.getClass().getMethod(methodName, classes(parameters));

				return true;
			} catch (Exception e) {
				for (Method method : node.getClass().getMethods()) {
					if (!method.getName().equals(methodName))
						continue;

					for (int i = 0; i < method.getParameterTypes().length; i++) {
						if (!method.getParameterTypes()[i].isAssignableFrom(parameters[i].getClass()))
							break;
						return true;
					}

					return false;
				}

				return false;
			}
		}

		public Object invoke(Object proxy, Method method, Object[] parameters) throws Throwable {
			notifyMethod(node, method, parameters);

			if (!hasMethod(node, method.getName(), parameters)) {
				return method.invoke(node, parameters);
			}

			method.invoke(node, parameters);

			return proxy;
		}
	}

	@Override
	public Object createNode(Class aInterface) {
		return newProxy(aInterface);
	}

	public Object newProxy(Class interfaceClass) {
		Class aClass = concreteClass(interfaceClass);

		InvocationHandler invocationHandler = new ObservableNode(aClass);

		ClassLoader classLoader = getClass().getClassLoader();

		Class[] interfaces = new Class[] { interfaceClass };

		Object proxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

		notifyNew(interfaceClass, proxy);

		return proxy;
	}

	public void notifyMethod(Object object, Method method, Object[] parameters) {
		if (method.getName().matches("toString"))
			return;

		StringBuffer stringBuffer = new StringBuffer();

		stringBuffer.append(object).append(":");

		stringBuffer.append(method.getName()).append("(");

		for (int i = 0; null != parameters && i < parameters.length; i++)
			stringBuffer.append(parameters[i]).append(", ");

		stringBuffer.append(")");

		System.out.println(stringBuffer.toString().replaceAll(", \\)$", "\\)"));
	}

	public void notifyNew(Class nodeInterface, Object proxy) {
		System.out.println("new" + nodeInterface.getSimpleName() + ":" + proxy);
	}
}